﻿/*  CamHelper (See http://camhelper.codeplex.com/ or https://bitbucket.org/AndiKS/camhelper for more Info)
    Copyright (C) 2011 Andreas Wimmer

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.Text;

namespace CamHelper.Interpreter
{
    public class Parser : IParser
    {
        public List<String> Tokens { get; private set; }
        private List<String> _tokensLower;
        private StringBuilder _currentToken;
        public StatementCollection Statements { get; private set; }
        public IParameterCollection Parameter { get; set; }

        public Parser()
        {
            Parameter = new ParameterCollection();
        }

        #region Scan

        public static List<String> StaticScan(string cmd)
        {
            var p = new Parser();
            p.Scan(cmd);
            return p.Tokens;
        }

        private void Scan(string cmd)
        {
            Tokens = new List<string>();
            _tokensLower = new List<string>();
            _currentToken = new StringBuilder();

            for (int i = 0; i < cmd.Length; i++)
            {
                var c = cmd[i];
                var c2 = (cmd.Length > i + 2 ? cmd.Substring(i, 2) : String.Empty);

                if (c2 == "<=" || c2 == ">=" || c2 == "==" || c2 == "!=" || c2 == "&&" || c2 == "||")
                {
                    FillTokens();

                    Tokens.Add(c2);
                    _tokensLower.Add(c2);
                    i++;
                }
                else if (c == ' ' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '=' || c == '?' || c == '&' || c == '<' || c == '>' || c == '(' || c == ')' || c == ':' || c == ',')
                {
                    FillTokens();

                    Tokens.Add(c.ToString());
                    _tokensLower.Add(c.ToString());
                }
                else
                {
                    _currentToken.Append(c);
                }

            }

            FillTokens();
        }

        private void FillTokens()
        {
            if (_currentToken.Length > 0)
            {
                var str = _currentToken.ToString();
                Tokens.Add(str);
                _tokensLower.Add(str.ToLower());
                _currentToken = new StringBuilder();
            }
        }

        #endregion

        #region SearchParameter

        public IParameterCollection SearchParameter(string cmd)
        {
            return SearchParameter(cmd, new ParameterCollection());
        }

        public IParameterCollection SearchParameter(string cmd, IParameterCollection input)
        {
            var output = new ParameterCollection();
            SearchParameter(cmd, input, output);
            return output;
        }

        private void SearchParameter(string cmd, IParameterCollection input, IParameterCollection output)
        {
            Scan(cmd);

            for (int i = 0; i < Tokens.Count; i++)
            {
                var item = Tokens[i];
                var firstChar = item[0];

                if (item.Length > 3 && (!Char.IsNumber(firstChar) && !Char.IsSymbol(firstChar)))
                {
                    var lowerItem = _tokensLower[i];
                    if (lowerItem == "round")
                        continue;

                    if (input.Contains(lowerItem))
                    {
                        var p = input[lowerItem];
                        output.Add(p);
                        if (p.Value.Length > 0)
                        {
                            var parser = new Parser();
                            parser.SearchParameter(p.Value, input, output);
                        }
                    }
                    else
                    {
                        var p = new Parameter(item);
                        output.Add(p);
                    }
                }
            }
        }

        #endregion

        #region Parse

        public static List<Statement> StaticParse(List<String> tokens)
        {
            var p = new Parser();
            p.Tokens = tokens;

            // HACK: all static methods should be in Unittestclass
            p._tokensLower = new List<string>();
            foreach (var item in p.Tokens)
            {
                p._tokensLower.Add(item.ToLower());
            }

            return p.Parse();
        }

        private List<Statement> Parse()
        {
            Statements = new StatementCollection();

            #region Search Parameter

            if (Parameter != null && Parameter.Count > 0)
            {
                for (int i = 0; i < Tokens.Count; i++)
                {
                    if (Parameter.Contains(_tokensLower[i]))
                    {
                        var pr = new ParameterReference(i, Parameter[_tokensLower[i]], this);
                        Statements.Add(i, pr);
                    }
                }
            }

            #endregion

            #region Search Operation, Parentheses and Condition

            var openParentheses = new Stack<Parentheses>();
            var openConditions = new Stack<Condition>();

            for (int i = 0; i < Tokens.Count; i++)
            {
                var token = Tokens[i];
                var tokenLower = _tokensLower[i];

                if (token == "(")
                {
                    var p = new Parentheses(i, this);
                    openParentheses.Push(p);
                    Statements.Add(i, p);
                }
                else if (token == ")")
                {
                    var p = openParentheses.Pop();
                    p.EndToken = i;
                    Statements.Add(i, p, false);
                }

                else if (token == "?")
                {
                    var c = new Condition(i, this);
                    openConditions.Push(c);
                    Statements.Add(i, c);
                }
                else if (token == ":")
                {
                    var c = openConditions.Pop();
                    c.EndToken = i;
                    Statements.Add(i, c, false);
                }

                else
                {
                    var operationType = OperationType.Addition;
                    var functionType = FunctionType.Round;
                    bool foundOperation = true;
                    bool foundFunction = true;

                    #region switch OperationType

                    switch (token)
                    {
                        case "+":
                            operationType = OperationType.Addition;
                            break;

                        case "-":
                            operationType = OperationType.Subtraction;
                            break;

                        case "*":
                            operationType = OperationType.Multiplication;
                            break;

                        case "/":
                            operationType = OperationType.Division;
                            break;

                        case "%":
                            operationType = OperationType.Remainder;
                            break;

                        case "<":
                            operationType = OperationType.LessThan;
                            break;

                        case ">":
                            operationType = OperationType.GreaterThan;
                            break;

                        case "<=":
                            operationType = OperationType.LessThanOrEqual;
                            break;

                        case ">=":
                            operationType = OperationType.GreaterThanOrEqual;
                            break;

                        case "==":
                            operationType = OperationType.Equal;
                            break;

                        case "!=":
                            operationType = OperationType.NotEqual;
                            break;

                        case "=":
                            operationType = OperationType.Assignment;
                            break;

                        case "&":
                            operationType = OperationType.LogicalAnd;
                            break;

                        case "&&":
                            operationType = OperationType.ConditionalAnd;
                            break;

                        case "||":
                            operationType = OperationType.ConditionalOr;
                            break;

                        case ",":
                            operationType = OperationType.Comma;
                            break;

                        default:
                            foundOperation = false;
                            break;
                    }

                    #endregion

                    #region switch FunctionType

                    switch (tokenLower)
                    {
                        case "round":
                            functionType = FunctionType.Round;
                            break;

                        default:
                            foundFunction = false;
                            break;
                    }

                    #endregion

                    if (foundOperation)
                    {
                        var op = new Operation(i, operationType, this);
                        Statements.Add(i, op);
                    }
                    else if (foundFunction)
                    {
                        var f = new Function(i, functionType, this);
                        Statements.Add(i, f);
                    }
                    else if (!Statements.ByIndex.ContainsKey(i))
                    {
                        var lit = new Literal(i, token, this);
                        Statements.Add(i, lit, false); // HACK: should Parsed to true or another solution....
                    }
                }

            }

            Statements.Sort();
            foreach (var op in Statements.Sorted)
            {
                ParseStatement(op);
            }

            #endregion

            #region return Tree

            var parseTree = new List<Statement>();
            if (Statements.ByIndex.Count > 0)
            {
                int index = 0;
                while (true)
                {
                    var stm = Statements.ByIndex[index];
                    parseTree.Add(stm);

                    index = stm.EndToken + 1;
                    if (index >= Tokens.Count)
                        break;
                }
            }

            return parseTree;

            #endregion

        }

        #endregion

        #region ParseStatement

        private void ParseStatement(Statement stm)
        {
            OnParsing(new ParseEventArgs(this, stm));
            bool setInDictionary = true;

            if (stm is Operation)
            {
                var op = stm as Operation;

                op.Left = GetNextStatementNotEmptyLiteral(op.StartToken, true);
                op.Right = GetNextStatementNotEmptyLiteral(op.EndToken, false);

                if (op.Type == OperationType.Subtraction && (op.Left == null || op.Left is Condition))
                {
                    var negativ = new NegativNumber(stm.StartToken, this);
                    negativ.Content = op.Right;
                    negativ.EndToken = negativ.Content.EndToken;
                    stm = negativ;
                }
                else
                {
                    op.StartToken = op.Left.StartToken;
                    op.EndToken = op.Right.EndToken;
                }
            }
            else if (stm is Function)
            {
                var f = stm as Function;
                var content = GetNextStatementNotEmptyLiteral(stm.StartToken, false) as Parentheses;
                f.Content = content;
                f.EndToken = f.Content.EndToken;
            }
            else if (stm is Parentheses)
            {
                var p = stm as Parentheses;
                p.Content = GetNextStatementNotEmptyLiteral(stm.StartToken, false);
                if (p.Included)
                {
                    setInDictionary = false;
                }
            }
            else if (stm is Condition)
            {
                var c = stm as Condition;
                c.Content = GetNextStatementNotEmptyLiteral(c.StartToken, true);
                c.FirstTrue = GetNextStatementNotEmptyLiteral(c.StartToken, false);
                c.StartToken = c.Content.StartToken;

                c.SecondFalse = GetNextStatementNotEmptyLiteral(c.EndToken, false);
                c.EndToken = c.SecondFalse.EndToken;
            }

            if (setInDictionary)
            {
                Statements.ByIndex[stm.StartToken] = stm;
                Statements.ByIndex[stm.EndToken] = stm;
            }
            stm.Command = GetTokens(stm.StartToken, stm.EndToken);
            stm.Parsed = true;
            OnParsed(new ParseEventArgs(this, stm));
        }

        #endregion

        #region GetNextStatementNotEmptyLiteral

        private Statement GetNextStatementNotEmptyLiteral(int startIndex, bool reverse)
        {
            do
            {
                if (reverse)
                    startIndex--;
                else
                    startIndex++;

                if (Statements.ByIndex.ContainsKey(startIndex))
                {
                    var stm = Statements.ByIndex[startIndex];
                    var lit = stm as Literal;
                    if (lit != null && lit.Word == " ")
                    {
                        Statements.ByIndex.Remove(startIndex);
                    }
                    else
                    {
                        if (stm is Parentheses)
                        {
                            var p = stm as Parentheses;
                            p.Included = !p.Parsed;
                        }
                        return stm;
                    }
                }
            }
            while (startIndex >= 0 && startIndex < Tokens.Count);

            return null;
        }

        #endregion

        #region Calc

        public static string StaticCalc(string cmd)
        {
            var p = new Parser();
            return p.Calc(cmd);
        }

        public string Calc(string cmd)
        {
            Scan(cmd);
            var tree = Parse();
            string result = String.Empty;

            foreach (var item in tree)
            {
                result += item.Calc();
            }

            return result;
        }

        #endregion

        #region Events

        public event EventHandler<ParseEventArgs> Parsing;
        public event EventHandler<ParseEventArgs> Parsed;
        public event EventHandler<ParseEventArgs> Calculating;
        public event EventHandler<ParseEventArgs> Calculated;

        protected virtual void OnParsing(ParseEventArgs args)
        {
            var handler = Parsing;
            if (handler != null)
            {
                handler(this, args);
            }
        }

        protected virtual void OnParsed(ParseEventArgs args)
        {
            var handler = Parsed;
            if (handler != null)
            {
                handler(this, args);
            }
        }

        public virtual void OnCalculating(ParseEventArgs args)
        {
            var handler = Calculating;
            if (handler != null)
            {
                handler(this, args);
            }
        }

        public virtual void OnCalculated(ParseEventArgs args)
        {
            var handler = Calculated;
            if (handler != null)
            {
                handler(this, args);
            }
        }

        #endregion

        #region GetTokens

        public string GetTokens(int start, int end)
        {
            string s = String.Empty;

            for (int i = start; i <= end; i++)
            {
                s += Tokens[i];
            }

            return s;
        }

        #endregion
    }
}
